/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing.text.html;

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.accessibility.*;
import java.text.BreakIterator;

/*
 * The AccessibleHTML class provide information about the contents
 * of a HTML document to assistive technologies.
 *
 * @author  Lynn Monsanto
 */
class AccessibleHTML implements Accessible {

  /**
   * The editor.
   */
  private JEditorPane editor;
  /**
   * Current model.
   */
  private Document model;
  /**
   * DocumentListener installed on the current model.
   */
  private DocumentListener docListener;
  /**
   * PropertyChangeListener installed on the editor
   */
  private PropertyChangeListener propChangeListener;
  /**
   * The root ElementInfo for the document
   */
  private ElementInfo rootElementInfo;
  /*
   * The root accessible context for the document
   */
  private RootHTMLAccessibleContext rootHTMLAccessibleContext;

  public AccessibleHTML(JEditorPane pane) {
    editor = pane;
    propChangeListener = new PropertyChangeHandler();
    setDocument(editor.getDocument());

    docListener = new DocumentHandler();
  }

  /**
   * Sets the document.
   */
  private void setDocument(Document document) {
    if (model != null) {
      model.removeDocumentListener(docListener);
    }
    if (editor != null) {
      editor.removePropertyChangeListener(propChangeListener);
    }
    this.model = document;
    if (model != null) {
      if (rootElementInfo != null) {
        rootElementInfo.invalidate(false);
      }
      buildInfo();
      model.addDocumentListener(docListener);
    } else {
      rootElementInfo = null;
    }
    if (editor != null) {
      editor.addPropertyChangeListener(propChangeListener);
    }
  }

  /**
   * Returns the Document currently presenting information for.
   */
  private Document getDocument() {
    return model;
  }

  /**
   * Returns the JEditorPane providing information for.
   */
  private JEditorPane getTextComponent() {
    return editor;
  }

  /**
   * Returns the ElementInfo representing the root Element.
   */
  private ElementInfo getRootInfo() {
    return rootElementInfo;
  }

  /**
   * Returns the root <code>View</code> associated with the current text
   * component.
   */
  private View getRootView() {
    return getTextComponent().getUI().getRootView(getTextComponent());
  }

  /**
   * Returns the bounds the root View will be rendered in.
   */
  private Rectangle getRootEditorRect() {
    Rectangle alloc = getTextComponent().getBounds();
    if ((alloc.width > 0) && (alloc.height > 0)) {
      alloc.x = alloc.y = 0;
      Insets insets = editor.getInsets();
      alloc.x += insets.left;
      alloc.y += insets.top;
      alloc.width -= insets.left + insets.right;
      alloc.height -= insets.top + insets.bottom;
      return alloc;
    }
    return null;
  }

  /**
   * If possible acquires a lock on the Document.  If a lock has been
   * obtained a key will be retured that should be passed to
   * <code>unlock</code>.
   */
  private Object lock() {
    Document document = getDocument();

    if (document instanceof AbstractDocument) {
      ((AbstractDocument) document).readLock();
      return document;
    }
    return null;
  }

  /**
   * Releases a lock previously obtained via <code>lock</code>.
   */
  private void unlock(Object key) {
    if (key != null) {
      ((AbstractDocument) key).readUnlock();
    }
  }

  /**
   * Rebuilds the information from the current info.
   */
  private void buildInfo() {
    Object lock = lock();

    try {
      Document doc = getDocument();
      Element root = doc.getDefaultRootElement();

      rootElementInfo = new ElementInfo(root);
      rootElementInfo.validate();
    } finally {
      unlock(lock);
    }
  }

  /*
   * Create an ElementInfo subclass based on the passed in Element.
   */
  ElementInfo createElementInfo(Element e, ElementInfo parent) {
    AttributeSet attrs = e.getAttributes();

    if (attrs != null) {
      Object name = attrs.getAttribute(StyleConstants.NameAttribute);

      if (name == HTML.Tag.IMG) {
        return new IconElementInfo(e, parent);
      } else if (name == HTML.Tag.CONTENT || name == HTML.Tag.CAPTION) {
        return new TextElementInfo(e, parent);
      } else if (name == HTML.Tag.TABLE) {
        return new TableElementInfo(e, parent);
      }
    }
    return null;
  }

  /**
   * Returns the root AccessibleContext for the document
   */
  public AccessibleContext getAccessibleContext() {
    if (rootHTMLAccessibleContext == null) {
      rootHTMLAccessibleContext =
          new RootHTMLAccessibleContext(rootElementInfo);
    }
    return rootHTMLAccessibleContext;
  }

  /*
   * The roow AccessibleContext for the document
   */
  private class RootHTMLAccessibleContext extends HTMLAccessibleContext {

    public RootHTMLAccessibleContext(ElementInfo elementInfo) {
      super(elementInfo);
    }

    /**
     * Gets the accessibleName property of this object.  The accessibleName
     * property of an object is a localized String that designates the purpose
     * of the object.  For example, the accessibleName property of a label
     * or button might be the text of the label or button itself.  In the
     * case of an object that doesn't display its name, the accessibleName
     * should still be set.  For example, in the case of a text field used
     * to enter the name of a city, the accessibleName for the en_US locale
     * could be 'city.'
     *
     * @return the localized name of the object; null if this object does not have a name
     * @see #setAccessibleName
     */
    public String getAccessibleName() {
      if (model != null) {
        return (String) model.getProperty(Document.TitleProperty);
      } else {
        return null;
      }
    }

    /**
     * Gets the accessibleDescription property of this object.  If this
     * property isn't set, returns the content type of this
     * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
     *
     * @return the localized description of the object; <code>null</code> if this object does not
     * have a description
     * @see #setAccessibleName
     */
    public String getAccessibleDescription() {
      return editor.getContentType();
    }

    /**
     * Gets the role of this object.  The role of the object is the generic
     * purpose or use of the class of this object.  For example, the role
     * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
     * AccessibleRole are provided so component developers can pick from
     * a set of predefined roles.  This enables assistive technologies to
     * provide a consistent interface to various tweaked subclasses of
     * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
     * that act like a push button) as well as distinguish between subclasses
     * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
     * and AccessibleRole.RADIO_BUTTON for radio buttons).
     * <p>Note that the AccessibleRole class is also extensible, so
     * custom component developers can define their own AccessibleRole's
     * if the set of predefined roles is inadequate.
     *
     * @return an instance of AccessibleRole describing the role of the object
     * @see AccessibleRole
     */
    public AccessibleRole getAccessibleRole() {
      return AccessibleRole.TEXT;
    }
  }

  /*
   * Base AccessibleContext class for HTML elements
   */
  protected abstract class HTMLAccessibleContext extends AccessibleContext
      implements Accessible, AccessibleComponent {

    protected ElementInfo elementInfo;

    public HTMLAccessibleContext(ElementInfo elementInfo) {
      this.elementInfo = elementInfo;
    }

    // begin AccessibleContext implementation ...
    public AccessibleContext getAccessibleContext() {
      return this;
    }

    /**
     * Gets the state set of this object.
     *
     * @return an instance of AccessibleStateSet describing the states of the object
     * @see AccessibleStateSet
     */
    public AccessibleStateSet getAccessibleStateSet() {
      AccessibleStateSet states = new AccessibleStateSet();
      Component comp = getTextComponent();

      if (comp.isEnabled()) {
        states.add(AccessibleState.ENABLED);
      }
      if (comp instanceof JTextComponent &&
          ((JTextComponent) comp).isEditable()) {

        states.add(AccessibleState.EDITABLE);
        states.add(AccessibleState.FOCUSABLE);
      }
      if (comp.isVisible()) {
        states.add(AccessibleState.VISIBLE);
      }
      if (comp.isShowing()) {
        states.add(AccessibleState.SHOWING);
      }
      return states;
    }

    /**
     * Gets the 0-based index of this object in its accessible parent.
     *
     * @return the 0-based index of this object in its parent; -1 if this object does not have an
     * accessible parent.
     * @see #getAccessibleParent
     * @see #getAccessibleChildrenCount
     * @see #getAccessibleChild
     */
    public int getAccessibleIndexInParent() {
      return elementInfo.getIndexInParent();
    }

    /**
     * Returns the number of accessible children of the object.
     *
     * @return the number of accessible children of the object.
     */
    public int getAccessibleChildrenCount() {
      return elementInfo.getChildCount();
    }

    /**
     * Returns the specified Accessible child of the object.  The Accessible
     * children of an Accessible object are zero-based, so the first child
     * of an Accessible child is at index 0, the second child is at index 1,
     * and so on.
     *
     * @param i zero-based index of child
     * @return the Accessible child of the object
     * @see #getAccessibleChildrenCount
     */
    public Accessible getAccessibleChild(int i) {
      ElementInfo childInfo = elementInfo.getChild(i);
      if (childInfo != null && childInfo instanceof Accessible) {
        return (Accessible) childInfo;
      } else {
        return null;
      }
    }

    /**
     * Gets the locale of the component. If the component does not have a
     * locale, then the locale of its parent is returned.
     *
     * @return this component's locale.  If this component does not have a locale, the locale of its
     * parent is returned.
     * @throws IllegalComponentStateException If the Component does not have its own locale and has
     * not yet been added to a containment hierarchy such that the locale can be determined from the
     * containing parent.
     */
    public Locale getLocale() throws IllegalComponentStateException {
      return editor.getLocale();
    }
    // ... end AccessibleContext implementation

    // begin AccessibleComponent implementation ...
    public AccessibleComponent getAccessibleComponent() {
      return this;
    }

    /**
     * Gets the background color of this object.
     *
     * @return the background color, if supported, of the object; otherwise, null
     * @see #setBackground
     */
    public Color getBackground() {
      return getTextComponent().getBackground();
    }

    /**
     * Sets the background color of this object.
     *
     * @param c the new Color for the background
     * @see #setBackground
     */
    public void setBackground(Color c) {
      getTextComponent().setBackground(c);
    }

    /**
     * Gets the foreground color of this object.
     *
     * @return the foreground color, if supported, of the object; otherwise, null
     * @see #setForeground
     */
    public Color getForeground() {
      return getTextComponent().getForeground();
    }

    /**
     * Sets the foreground color of this object.
     *
     * @param c the new Color for the foreground
     * @see #getForeground
     */
    public void setForeground(Color c) {
      getTextComponent().setForeground(c);
    }

    /**
     * Gets the Cursor of this object.
     *
     * @return the Cursor, if supported, of the object; otherwise, null
     * @see #setCursor
     */
    public Cursor getCursor() {
      return getTextComponent().getCursor();
    }

    /**
     * Sets the Cursor of this object.
     *
     * @param cursor the new Cursor for the object
     * @see #getCursor
     */
    public void setCursor(Cursor cursor) {
      getTextComponent().setCursor(cursor);
    }

    /**
     * Gets the Font of this object.
     *
     * @return the Font,if supported, for the object; otherwise, null
     * @see #setFont
     */
    public Font getFont() {
      return getTextComponent().getFont();
    }

    /**
     * Sets the Font of this object.
     *
     * @param f the new Font for the object
     * @see #getFont
     */
    public void setFont(Font f) {
      getTextComponent().setFont(f);
    }

    /**
     * Gets the FontMetrics of this object.
     *
     * @param f the Font
     * @return the FontMetrics, if supported, the object; otherwise, null
     * @see #getFont
     */
    public FontMetrics getFontMetrics(Font f) {
      return getTextComponent().getFontMetrics(f);
    }

    /**
     * Determines if the object is enabled.  Objects that are enabled
     * will also have the AccessibleState.ENABLED state set in their
     * AccessibleStateSets.
     *
     * @return true if object is enabled; otherwise, false
     * @see #setEnabled
     * @see AccessibleContext#getAccessibleStateSet
     * @see AccessibleState#ENABLED
     * @see AccessibleStateSet
     */
    public boolean isEnabled() {
      return getTextComponent().isEnabled();
    }

    /**
     * Sets the enabled state of the object.
     *
     * @param b if true, enables this object; otherwise, disables it
     * @see #isEnabled
     */
    public void setEnabled(boolean b) {
      getTextComponent().setEnabled(b);
    }

    /**
     * Determines if the object is visible.  Note: this means that the
     * object intends to be visible; however, it may not be
     * showing on the screen because one of the objects that this object
     * is contained by is currently not visible.  To determine if an object
     * is showing on the screen, use isShowing().
     * <p>Objects that are visible will also have the
     * AccessibleState.VISIBLE state set in their AccessibleStateSets.
     *
     * @return true if object is visible; otherwise, false
     * @see #setVisible
     * @see AccessibleContext#getAccessibleStateSet
     * @see AccessibleState#VISIBLE
     * @see AccessibleStateSet
     */
    public boolean isVisible() {
      return getTextComponent().isVisible();
    }

    /**
     * Sets the visible state of the object.
     *
     * @param b if true, shows this object; otherwise, hides it
     * @see #isVisible
     */
    public void setVisible(boolean b) {
      getTextComponent().setVisible(b);
    }

    /**
     * Determines if the object is showing.  This is determined by checking
     * the visibility of the object and its ancestors.
     * Note: this
     * will return true even if the object is obscured by another (for
     * example, it is underneath a menu that was pulled down).
     *
     * @return true if object is showing; otherwise, false
     */
    public boolean isShowing() {
      return getTextComponent().isShowing();
    }

    /**
     * Checks whether the specified point is within this object's bounds,
     * where the point's x and y coordinates are defined to be relative
     * to the coordinate system of the object.
     *
     * @param p the Point relative to the coordinate system of the object
     * @return true if object contains Point; otherwise false
     * @see #getBounds
     */
    public boolean contains(Point p) {
      Rectangle r = getBounds();
      if (r != null) {
        return r.contains(p.x, p.y);
      } else {
        return false;
      }
    }

    /**
     * Returns the location of the object on the screen.
     *
     * @return the location of the object on screen; null if this object is not on the screen
     * @see #getBounds
     * @see #getLocation
     */
    public Point getLocationOnScreen() {
      Point editorLocation = getTextComponent().getLocationOnScreen();
      Rectangle r = getBounds();
      if (r != null) {
        return new Point(editorLocation.x + r.x,
            editorLocation.y + r.y);
      } else {
        return null;
      }
    }

    /**
     * Gets the location of the object relative to the parent in the form
     * of a point specifying the object's top-left corner in the screen's
     * coordinate space.
     *
     * @return An instance of Point representing the top-left corner of the object's bounds in the
     * coordinate space of the screen; null if this object or its parent are not on the screen
     * @see #getBounds
     * @see #getLocationOnScreen
     */
    public Point getLocation() {
      Rectangle r = getBounds();
      if (r != null) {
        return new Point(r.x, r.y);
      } else {
        return null;
      }
    }

    /**
     * Sets the location of the object relative to the parent.
     *
     * @param p the new position for the top-left corner
     * @see #getLocation
     */
    public void setLocation(Point p) {
    }

    /**
     * Gets the bounds of this object in the form of a Rectangle object.
     * The bounds specify this object's width, height, and location
     * relative to its parent.
     *
     * @return A rectangle indicating this component's bounds; null if this object is not on the
     * screen.
     * @see #contains
     */
    public Rectangle getBounds() {
      return elementInfo.getBounds();
    }

    /**
     * Sets the bounds of this object in the form of a Rectangle object.
     * The bounds specify this object's width, height, and location
     * relative to its parent.
     *
     * @param r rectangle indicating this component's bounds
     * @see #getBounds
     */
    public void setBounds(Rectangle r) {
    }

    /**
     * Returns the size of this object in the form of a Dimension object.
     * The height field of the Dimension object contains this object's
     * height, and the width field of the Dimension object contains this
     * object's width.
     *
     * @return A Dimension object that indicates the size of this component; null if this object is
     * not on the screen
     * @see #setSize
     */
    public Dimension getSize() {
      Rectangle r = getBounds();
      if (r != null) {
        return new Dimension(r.width, r.height);
      } else {
        return null;
      }
    }

    /**
     * Resizes this object so that it has width and height.
     *
     * @param d The dimension specifying the new size of the object.
     * @see #getSize
     */
    public void setSize(Dimension d) {
      Component comp = getTextComponent();
      comp.setSize(d);
    }

    /**
     * Returns the Accessible child, if one exists, contained at the local
     * coordinate Point.
     *
     * @param p The point relative to the coordinate system of this object.
     * @return the Accessible, if it exists, at the specified location; otherwise null
     */
    public Accessible getAccessibleAt(Point p) {
      ElementInfo innerMostElement = getElementInfoAt(rootElementInfo, p);
      if (innerMostElement instanceof Accessible) {
        return (Accessible) innerMostElement;
      } else {
        return null;
      }
    }

    private ElementInfo getElementInfoAt(ElementInfo elementInfo, Point p) {
      if (elementInfo.getBounds() == null) {
        return null;
      }
      if (elementInfo.getChildCount() == 0 &&
          elementInfo.getBounds().contains(p)) {
        return elementInfo;

      } else {
        if (elementInfo instanceof TableElementInfo) {
          // Handle table caption as a special case since it's the
          // only table child that is not a table row.
          ElementInfo captionInfo =
              ((TableElementInfo) elementInfo).getCaptionInfo();
          if (captionInfo != null) {
            Rectangle bounds = captionInfo.getBounds();
            if (bounds != null && bounds.contains(p)) {
              return captionInfo;
            }
          }
        }
        for (int i = 0; i < elementInfo.getChildCount(); i++) {
          ElementInfo childInfo = elementInfo.getChild(i);
          ElementInfo retValue = getElementInfoAt(childInfo, p);
          if (retValue != null) {
            return retValue;
          }
        }
      }
      return null;
    }

    /**
     * Returns whether this object can accept focus or not.   Objects that
     * can accept focus will also have the AccessibleState.FOCUSABLE state
     * set in their AccessibleStateSets.
     *
     * @return true if object can accept focus; otherwise false
     * @see AccessibleContext#getAccessibleStateSet
     * @see AccessibleState#FOCUSABLE
     * @see AccessibleState#FOCUSED
     * @see AccessibleStateSet
     */
    public boolean isFocusTraversable() {
      Component comp = getTextComponent();
      if (comp instanceof JTextComponent) {
        if (((JTextComponent) comp).isEditable()) {
          return true;
        }
      }
      return false;
    }

    /**
     * Requests focus for this object.  If this object cannot accept focus,
     * nothing will happen.  Otherwise, the object will attempt to take
     * focus.
     *
     * @see #isFocusTraversable
     */
    public void requestFocus() {
      // TIGER - 4856191
      if (!isFocusTraversable()) {
        return;
      }

      Component comp = getTextComponent();
      if (comp instanceof JTextComponent) {

        comp.requestFocusInWindow();

        try {
          if (elementInfo.validateIfNecessary()) {
            // set the caret position to the start of this component
            Element elem = elementInfo.getElement();
            ((JTextComponent) comp).setCaretPosition(elem.getStartOffset());

            // fire a AccessibleState.FOCUSED property change event
            AccessibleContext ac = editor.getAccessibleContext();
            PropertyChangeEvent pce = new PropertyChangeEvent(this,
                AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
                null, AccessibleState.FOCUSED);
            ac.firePropertyChange(
                AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
                null, pce);
          }
        } catch (IllegalArgumentException e) {
          // don't fire property change event
        }
      }
    }

    /**
     * Adds the specified focus listener to receive focus events from this
     * component.
     *
     * @param l the focus listener
     * @see #removeFocusListener
     */
    public void addFocusListener(FocusListener l) {
      getTextComponent().addFocusListener(l);
    }

    /**
     * Removes the specified focus listener so it no longer receives focus
     * events from this component.
     *
     * @param l the focus listener
     * @see #addFocusListener
     */
    public void removeFocusListener(FocusListener l) {
      getTextComponent().removeFocusListener(l);
    }
    // ... end AccessibleComponent implementation
  } // ... end HTMLAccessibleContext


  /*
   * ElementInfo for text
   */
  class TextElementInfo extends ElementInfo implements Accessible {

    TextElementInfo(Element element, ElementInfo parent) {
      super(element, parent);
    }

    // begin AccessibleText implementation ...
    private AccessibleContext accessibleContext;

    public AccessibleContext getAccessibleContext() {
      if (accessibleContext == null) {
        accessibleContext = new TextAccessibleContext(this);
      }
      return accessibleContext;
    }

    /*
     * AccessibleContext for text elements
     */
    public class TextAccessibleContext extends HTMLAccessibleContext
        implements AccessibleText {

      public TextAccessibleContext(ElementInfo elementInfo) {
        super(elementInfo);
      }

      public AccessibleText getAccessibleText() {
        return this;
      }

      /**
       * Gets the accessibleName property of this object.  The accessibleName
       * property of an object is a localized String that designates the purpose
       * of the object.  For example, the accessibleName property of a label
       * or button might be the text of the label or button itself.  In the
       * case of an object that doesn't display its name, the accessibleName
       * should still be set.  For example, in the case of a text field used
       * to enter the name of a city, the accessibleName for the en_US locale
       * could be 'city.'
       *
       * @return the localized name of the object; null if this object does not have a name
       * @see #setAccessibleName
       */
      public String getAccessibleName() {
        if (model != null) {
          return (String) model.getProperty(Document.TitleProperty);
        } else {
          return null;
        }
      }

      /**
       * Gets the accessibleDescription property of this object.  If this
       * property isn't set, returns the content type of this
       * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
       *
       * @return the localized description of the object; <code>null</code> if this object does not
       * have a description
       * @see #setAccessibleName
       */
      public String getAccessibleDescription() {
        return editor.getContentType();
      }

      /**
       * Gets the role of this object.  The role of the object is the generic
       * purpose or use of the class of this object.  For example, the role
       * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
       * AccessibleRole are provided so component developers can pick from
       * a set of predefined roles.  This enables assistive technologies to
       * provide a consistent interface to various tweaked subclasses of
       * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
       * that act like a push button) as well as distinguish between subclasses
       * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
       * and AccessibleRole.RADIO_BUTTON for radio buttons).
       * <p>Note that the AccessibleRole class is also extensible, so
       * custom component developers can define their own AccessibleRole's
       * if the set of predefined roles is inadequate.
       *
       * @return an instance of AccessibleRole describing the role of the object
       * @see AccessibleRole
       */
      public AccessibleRole getAccessibleRole() {
        return AccessibleRole.TEXT;
      }

      /**
       * Given a point in local coordinates, return the zero-based index
       * of the character under that Point.  If the point is invalid,
       * this method returns -1.
       *
       * @param p the Point in local coordinates
       * @return the zero-based index of the character under Point p; if Point is invalid returns
       * -1.
       */
      public int getIndexAtPoint(Point p) {
        View v = getView();
        if (v != null) {
          return v.viewToModel(p.x, p.y, getBounds());
        } else {
          return -1;
        }
      }

      /**
       * Determine the bounding box of the character at the given
       * index into the string.  The bounds are returned in local
       * coordinates.  If the index is invalid an empty rectangle is
       * returned.
       *
       * @param i the index into the String
       * @return the screen coordinates of the character's the bounding box, if index is invalid
       * returns an empty rectangle.
       */
      public Rectangle getCharacterBounds(int i) {
        try {
          return editor.getUI().modelToView(editor, i);
        } catch (BadLocationException e) {
          return null;
        }
      }

      /**
       * Return the number of characters (valid indicies)
       *
       * @return the number of characters
       */
      public int getCharCount() {
        if (validateIfNecessary()) {
          Element elem = elementInfo.getElement();
          return elem.getEndOffset() - elem.getStartOffset();
        }
        return 0;
      }

      /**
       * Return the zero-based offset of the caret.
       *
       * Note: That to the right of the caret will have the same index
       * value as the offset (the caret is between two characters).
       *
       * @return the zero-based offset of the caret.
       */
      public int getCaretPosition() {
        View v = getView();
        if (v == null) {
          return -1;
        }
        Container c = v.getContainer();
        if (c == null) {
          return -1;
        }
        if (c instanceof JTextComponent) {
          return ((JTextComponent) c).getCaretPosition();
        } else {
          return -1;
        }
      }

      /**
       * IndexedSegment extends Segment adding the offset into the
       * the model the <code>Segment</code> was asked for.
       */
      private class IndexedSegment extends Segment {

        /**
         * Offset into the model that the position represents.
         */
        public int modelOffset;
      }

      public String getAtIndex(int part, int index) {
        return getAtIndex(part, index, 0);
      }


      public String getAfterIndex(int part, int index) {
        return getAtIndex(part, index, 1);
      }

      public String getBeforeIndex(int part, int index) {
        return getAtIndex(part, index, -1);
      }

      /**
       * Gets the word, sentence, or character at <code>index</code>.
       * If <code>direction</code> is non-null this will find the
       * next/previous word/sentence/character.
       */
      private String getAtIndex(int part, int index, int direction) {
        if (model instanceof AbstractDocument) {
          ((AbstractDocument) model).readLock();
        }
        try {
          if (index < 0 || index >= model.getLength()) {
            return null;
          }
          switch (part) {
            case AccessibleText.CHARACTER:
              if (index + direction < model.getLength() &&
                  index + direction >= 0) {
                return model.getText(index + direction, 1);
              }
              break;

            case AccessibleText.WORD:
            case AccessibleText.SENTENCE:
              IndexedSegment seg = getSegmentAt(part, index);
              if (seg != null) {
                if (direction != 0) {
                  int next;

                  if (direction < 0) {
                    next = seg.modelOffset - 1;
                  } else {
                    next = seg.modelOffset + direction * seg.count;
                  }
                  if (next >= 0 && next <= model.getLength()) {
                    seg = getSegmentAt(part, next);
                  } else {
                    seg = null;
                  }
                }
                if (seg != null) {
                  return new String(seg.array, seg.offset,
                      seg.count);
                }
              }
              break;

            default:
              break;
          }
        } catch (BadLocationException e) {
        } finally {
          if (model instanceof AbstractDocument) {
            ((AbstractDocument) model).readUnlock();
          }
        }
        return null;
      }

      /*
       * Returns the paragraph element for the specified index.
       */
      private Element getParagraphElement(int index) {
        if (model instanceof PlainDocument) {
          PlainDocument sdoc = (PlainDocument) model;
          return sdoc.getParagraphElement(index);
        } else if (model instanceof StyledDocument) {
          StyledDocument sdoc = (StyledDocument) model;
          return sdoc.getParagraphElement(index);
        } else {
          Element para;
          for (para = model.getDefaultRootElement(); !para.isLeaf(); ) {
            int pos = para.getElementIndex(index);
            para = para.getElement(pos);
          }
          if (para == null) {
            return null;
          }
          return para.getParentElement();
        }
      }

      /*
       * Returns a <code>Segment</code> containing the paragraph text
       * at <code>index</code>, or null if <code>index</code> isn't
       * valid.
       */
      private IndexedSegment getParagraphElementText(int index)
          throws BadLocationException {
        Element para = getParagraphElement(index);

        if (para != null) {
          IndexedSegment segment = new IndexedSegment();
          try {
            int length = para.getEndOffset() - para.getStartOffset();
            model.getText(para.getStartOffset(), length, segment);
          } catch (BadLocationException e) {
            return null;
          }
          segment.modelOffset = para.getStartOffset();
          return segment;
        }
        return null;
      }


      /**
       * Returns the Segment at <code>index</code> representing either
       * the paragraph or sentence as identified by <code>part</code>, or
       * null if a valid paragraph/sentence can't be found. The offset
       * will point to the start of the word/sentence in the array, and
       * the modelOffset will point to the location of the word/sentence
       * in the model.
       */
      private IndexedSegment getSegmentAt(int part, int index)
          throws BadLocationException {

        IndexedSegment seg = getParagraphElementText(index);
        if (seg == null) {
          return null;
        }
        BreakIterator iterator;
        switch (part) {
          case AccessibleText.WORD:
            iterator = BreakIterator.getWordInstance(getLocale());
            break;
          case AccessibleText.SENTENCE:
            iterator = BreakIterator.getSentenceInstance(getLocale());
            break;
          default:
            return null;
        }
        seg.first();
        iterator.setText(seg);
        int end = iterator.following(index - seg.modelOffset + seg.offset);
        if (end == BreakIterator.DONE) {
          return null;
        }
        if (end > seg.offset + seg.count) {
          return null;
        }
        int begin = iterator.previous();
        if (begin == BreakIterator.DONE ||
            begin >= seg.offset + seg.count) {
          return null;
        }
        seg.modelOffset = seg.modelOffset + begin - seg.offset;
        seg.offset = begin;
        seg.count = end - begin;
        return seg;
      }

      /**
       * Return the AttributeSet for a given character at a given index
       *
       * @param i the zero-based index into the text
       * @return the AttributeSet of the character
       */
      public AttributeSet getCharacterAttribute(int i) {
        if (model instanceof StyledDocument) {
          StyledDocument doc = (StyledDocument) model;
          Element elem = doc.getCharacterElement(i);
          if (elem != null) {
            return elem.getAttributes();
          }
        }
        return null;
      }

      /**
       * Returns the start offset within the selected text.
       * If there is no selection, but there is
       * a caret, the start and end offsets will be the same.
       *
       * @return the index into the text of the start of the selection
       */
      public int getSelectionStart() {
        return editor.getSelectionStart();
      }

      /**
       * Returns the end offset within the selected text.
       * If there is no selection, but there is
       * a caret, the start and end offsets will be the same.
       *
       * @return the index into the text of the end of the selection
       */
      public int getSelectionEnd() {
        return editor.getSelectionEnd();
      }

      /**
       * Returns the portion of the text that is selected.
       *
       * @return the String portion of the text that is selected
       */
      public String getSelectedText() {
        return editor.getSelectedText();
      }

      /*
       * Returns the text substring starting at the specified
       * offset with the specified length.
       */
      private String getText(int offset, int length)
          throws BadLocationException {

        if (model != null && model instanceof StyledDocument) {
          StyledDocument doc = (StyledDocument) model;
          return model.getText(offset, length);
        } else {
          return null;
        }
      }
    }
  }

  /*
   * ElementInfo for images
   */
  private class IconElementInfo extends ElementInfo implements Accessible {

    private int width = -1;
    private int height = -1;

    IconElementInfo(Element element, ElementInfo parent) {
      super(element, parent);
    }

    protected void invalidate(boolean first) {
      super.invalidate(first);
      width = height = -1;
    }

    private int getImageSize(Object key) {
      if (validateIfNecessary()) {
        int size = getIntAttr(getAttributes(), key, -1);

        if (size == -1) {
          View v = getView();

          size = 0;
          if (v instanceof ImageView) {
            Image img = ((ImageView) v).getImage();
            if (img != null) {
              if (key == HTML.Attribute.WIDTH) {
                size = img.getWidth(null);
              } else {
                size = img.getHeight(null);
              }
            }
          }
        }
        return size;
      }
      return 0;
    }

    // begin AccessibleIcon implementation ...
    private AccessibleContext accessibleContext;

    public AccessibleContext getAccessibleContext() {
      if (accessibleContext == null) {
        accessibleContext = new IconAccessibleContext(this);
      }
      return accessibleContext;
    }

    /*
     * AccessibleContext for images
     */
    protected class IconAccessibleContext extends HTMLAccessibleContext
        implements AccessibleIcon {

      public IconAccessibleContext(ElementInfo elementInfo) {
        super(elementInfo);
      }

      /**
       * Gets the accessibleName property of this object.  The accessibleName
       * property of an object is a localized String that designates the purpose
       * of the object.  For example, the accessibleName property of a label
       * or button might be the text of the label or button itself.  In the
       * case of an object that doesn't display its name, the accessibleName
       * should still be set.  For example, in the case of a text field used
       * to enter the name of a city, the accessibleName for the en_US locale
       * could be 'city.'
       *
       * @return the localized name of the object; null if this object does not have a name
       * @see #setAccessibleName
       */
      public String getAccessibleName() {
        return getAccessibleIconDescription();
      }

      /**
       * Gets the accessibleDescription property of this object.  If this
       * property isn't set, returns the content type of this
       * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
       *
       * @return the localized description of the object; <code>null</code> if this object does not
       * have a description
       * @see #setAccessibleName
       */
      public String getAccessibleDescription() {
        return editor.getContentType();
      }

      /**
       * Gets the role of this object.  The role of the object is the generic
       * purpose or use of the class of this object.  For example, the role
       * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
       * AccessibleRole are provided so component developers can pick from
       * a set of predefined roles.  This enables assistive technologies to
       * provide a consistent interface to various tweaked subclasses of
       * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
       * that act like a push button) as well as distinguish between subclasses
       * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
       * and AccessibleRole.RADIO_BUTTON for radio buttons).
       * <p>Note that the AccessibleRole class is also extensible, so
       * custom component developers can define their own AccessibleRole's
       * if the set of predefined roles is inadequate.
       *
       * @return an instance of AccessibleRole describing the role of the object
       * @see AccessibleRole
       */
      public AccessibleRole getAccessibleRole() {
        return AccessibleRole.ICON;
      }

      public AccessibleIcon[] getAccessibleIcon() {
        AccessibleIcon[] icons = new AccessibleIcon[1];
        icons[0] = this;
        return icons;
      }

      /**
       * Gets the description of the icon.  This is meant to be a brief
       * textual description of the object.  For example, it might be
       * presented to a blind user to give an indication of the purpose
       * of the icon.
       *
       * @return the description of the icon
       */
      public String getAccessibleIconDescription() {
        return ((ImageView) getView()).getAltText();
      }

      /**
       * Sets the description of the icon.  This is meant to be a brief
       * textual description of the object.  For example, it might be
       * presented to a blind user to give an indication of the purpose
       * of the icon.
       *
       * @param description the description of the icon
       */
      public void setAccessibleIconDescription(String description) {
      }

      /**
       * Gets the width of the icon
       *
       * @return the width of the icon.
       */
      public int getAccessibleIconWidth() {
        if (width == -1) {
          width = getImageSize(HTML.Attribute.WIDTH);
        }
        return width;
      }

      /**
       * Gets the height of the icon
       *
       * @return the height of the icon.
       */
      public int getAccessibleIconHeight() {
        if (height == -1) {
          height = getImageSize(HTML.Attribute.HEIGHT);
        }
        return height;
      }
    }
    // ... end AccessibleIconImplementation
  }


  /**
   * TableElementInfo encapsulates information about a HTML.Tag.TABLE.
   * To make access fast it crates a grid containing the children to
   * allow for access by row, column. TableElementInfo will contain
   * TableRowElementInfos, which will contain TableCellElementInfos.
   * Any time one of the rows or columns becomes invalid the table is
   * invalidated.  This is because any time one of the child attributes
   * changes the size of the grid may have changed.
   */
  private class TableElementInfo extends ElementInfo
      implements Accessible {

    protected ElementInfo caption;

    /**
     * Allocation of the table by row x column. There may be holes (eg
     * nulls) depending upon the html, any cell that has a rowspan/colspan
     * > 1 will be contained multiple times in the grid.
     */
    private TableCellElementInfo[][] grid;


    TableElementInfo(Element e, ElementInfo parent) {
      super(e, parent);
    }

    public ElementInfo getCaptionInfo() {
      return caption;
    }

    /**
     * Overriden to update the grid when validating.
     */
    protected void validate() {
      super.validate();
      updateGrid();
    }

    /**
     * Overriden to only alloc instances of TableRowElementInfos.
     */
    protected void loadChildren(Element e) {

      for (int counter = 0; counter < e.getElementCount(); counter++) {
        Element child = e.getElement(counter);
        AttributeSet attrs = child.getAttributes();

        if (attrs.getAttribute(StyleConstants.NameAttribute) ==
            HTML.Tag.TR) {
          addChild(new TableRowElementInfo(child, this, counter));

        } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
            HTML.Tag.CAPTION) {
          // Handle captions as a special case since all other
          // children are table rows.
          caption = createElementInfo(child, this);
        }
      }
    }

    /**
     * Updates the grid.
     */
    private void updateGrid() {
      // Determine the max row/col count.
      int delta = 0;
      int maxCols = 0;
      int rows;
      for (int counter = 0; counter < getChildCount(); counter++) {
        TableRowElementInfo row = getRow(counter);
        int prev = 0;
        for (int y = 0; y < delta; y++) {
          prev = Math.max(prev, getRow(counter - y - 1).
              getColumnCount(y + 2));
        }
        delta = Math.max(row.getRowCount(), delta);
        delta--;
        maxCols = Math.max(maxCols, row.getColumnCount() + prev);
      }
      rows = getChildCount() + delta;

      // Alloc
      grid = new TableCellElementInfo[rows][];
      for (int counter = 0; counter < rows; counter++) {
        grid[counter] = new TableCellElementInfo[maxCols];
      }
      // Update
      for (int counter = 0; counter < rows; counter++) {
        getRow(counter).updateGrid(counter);
      }
    }

    /**
     * Returns the TableCellElementInfo at the specified index.
     */
    public TableRowElementInfo getRow(int index) {
      return (TableRowElementInfo) getChild(index);
    }

    /**
     * Returns the TableCellElementInfo by row and column.
     */
    public TableCellElementInfo getCell(int r, int c) {
      if (validateIfNecessary() && r < grid.length &&
          c < grid[0].length) {
        return grid[r][c];
      }
      return null;
    }

    /**
     * Returns the rowspan of the specified entry.
     */
    public int getRowExtentAt(int r, int c) {
      TableCellElementInfo cell = getCell(r, c);

      if (cell != null) {
        int rows = cell.getRowCount();
        int delta = 1;

        while ((r - delta) >= 0 && grid[r - delta][c] == cell) {
          delta++;
        }
        return rows - delta + 1;
      }
      return 0;
    }

    /**
     * Returns the colspan of the specified entry.
     */
    public int getColumnExtentAt(int r, int c) {
      TableCellElementInfo cell = getCell(r, c);

      if (cell != null) {
        int cols = cell.getColumnCount();
        int delta = 1;

        while ((c - delta) >= 0 && grid[r][c - delta] == cell) {
          delta++;
        }
        return cols - delta + 1;
      }
      return 0;
    }

    /**
     * Returns the number of rows in the table.
     */
    public int getRowCount() {
      if (validateIfNecessary()) {
        return grid.length;
      }
      return 0;
    }

    /**
     * Returns the number of columns in the table.
     */
    public int getColumnCount() {
      if (validateIfNecessary() && grid.length > 0) {
        return grid[0].length;
      }
      return 0;
    }

    // begin AccessibleTable implementation ...
    private AccessibleContext accessibleContext;

    public AccessibleContext getAccessibleContext() {
      if (accessibleContext == null) {
        accessibleContext = new TableAccessibleContext(this);
      }
      return accessibleContext;
    }

    /*
     * AccessibleContext for tables
     */
    public class TableAccessibleContext extends HTMLAccessibleContext
        implements AccessibleTable {

      private AccessibleHeadersTable rowHeadersTable;

      public TableAccessibleContext(ElementInfo elementInfo) {
        super(elementInfo);
      }

      /**
       * Gets the accessibleName property of this object.  The accessibleName
       * property of an object is a localized String that designates the purpose
       * of the object.  For example, the accessibleName property of a label
       * or button might be the text of the label or button itself.  In the
       * case of an object that doesn't display its name, the accessibleName
       * should still be set.  For example, in the case of a text field used
       * to enter the name of a city, the accessibleName for the en_US locale
       * could be 'city.'
       *
       * @return the localized name of the object; null if this object does not have a name
       * @see #setAccessibleName
       */
      public String getAccessibleName() {
        // return the role of the object
        return getAccessibleRole().toString();
      }

      /**
       * Gets the accessibleDescription property of this object.  If this
       * property isn't set, returns the content type of this
       * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
       *
       * @return the localized description of the object; <code>null</code> if this object does not
       * have a description
       * @see #setAccessibleName
       */
      public String getAccessibleDescription() {
        return editor.getContentType();
      }

      /**
       * Gets the role of this object.  The role of the object is the generic
       * purpose or use of the class of this object.  For example, the role
       * of a push button is AccessibleRole.PUSH_BUTTON.  The roles in
       * AccessibleRole are provided so component developers can pick from
       * a set of predefined roles.  This enables assistive technologies to
       * provide a consistent interface to various tweaked subclasses of
       * components (e.g., use AccessibleRole.PUSH_BUTTON for all components
       * that act like a push button) as well as distinguish between subclasses
       * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes
       * and AccessibleRole.RADIO_BUTTON for radio buttons).
       * <p>Note that the AccessibleRole class is also extensible, so
       * custom component developers can define their own AccessibleRole's
       * if the set of predefined roles is inadequate.
       *
       * @return an instance of AccessibleRole describing the role of the object
       * @see AccessibleRole
       */
      public AccessibleRole getAccessibleRole() {
        return AccessibleRole.TABLE;
      }

      /**
       * Gets the 0-based index of this object in its accessible parent.
       *
       * @return the 0-based index of this object in its parent; -1 if this object does not have an
       * accessible parent.
       * @gsee #getAccessibleChild
       * @see #getAccessibleParent
       * @see #getAccessibleChildrenCount
       */
      public int getAccessibleIndexInParent() {
        return elementInfo.getIndexInParent();
      }

      /**
       * Returns the number of accessible children of the object.
       *
       * @return the number of accessible children of the object.
       */
      public int getAccessibleChildrenCount() {
        return ((TableElementInfo) elementInfo).getRowCount() *
            ((TableElementInfo) elementInfo).getColumnCount();
      }

      /**
       * Returns the specified Accessible child of the object.  The Accessible
       * children of an Accessible object are zero-based, so the first child
       * of an Accessible child is at index 0, the second child is at index 1,
       * and so on.
       *
       * @param i zero-based index of child
       * @return the Accessible child of the object
       * @see #getAccessibleChildrenCount
       */
      public Accessible getAccessibleChild(int i) {
        int rowCount = ((TableElementInfo) elementInfo).getRowCount();
        int columnCount = ((TableElementInfo) elementInfo).getColumnCount();
        int r = i / rowCount;
        int c = i % columnCount;
        if (r < 0 || r >= rowCount || c < 0 || c >= columnCount) {
          return null;
        } else {
          return getAccessibleAt(r, c);
        }
      }

      public AccessibleTable getAccessibleTable() {
        return this;
      }

      /**
       * Returns the caption for the table.
       *
       * @return the caption for the table
       */
      public Accessible getAccessibleCaption() {
        ElementInfo captionInfo = getCaptionInfo();
        if (captionInfo instanceof Accessible) {
          return (Accessible) caption;
        } else {
          return null;
        }
      }

      /**
       * Sets the caption for the table.
       *
       * @param a the caption for the table
       */
      public void setAccessibleCaption(Accessible a) {
      }

      /**
       * Returns the summary description of the table.
       *
       * @return the summary description of the table
       */
      public Accessible getAccessibleSummary() {
        return null;
      }

      /**
       * Sets the summary description of the table
       *
       * @param a the summary description of the table
       */
      public void setAccessibleSummary(Accessible a) {
      }

      /**
       * Returns the number of rows in the table.
       *
       * @return the number of rows in the table
       */
      public int getAccessibleRowCount() {
        return ((TableElementInfo) elementInfo).getRowCount();
      }

      /**
       * Returns the number of columns in the table.
       *
       * @return the number of columns in the table
       */
      public int getAccessibleColumnCount() {
        return ((TableElementInfo) elementInfo).getColumnCount();
      }

      /**
       * Returns the Accessible at a specified row and column
       * in the table.
       *
       * @param r zero-based row of the table
       * @param c zero-based column of the table
       * @return the Accessible at the specified row and column
       */
      public Accessible getAccessibleAt(int r, int c) {
        TableCellElementInfo cellInfo = getCell(r, c);
        if (cellInfo != null) {
          return cellInfo.getAccessible();
        } else {
          return null;
        }
      }

      /**
       * Returns the number of rows occupied by the Accessible at
       * a specified row and column in the table.
       *
       * @return the number of rows occupied by the Accessible at a given specified (row, column)
       */
      public int getAccessibleRowExtentAt(int r, int c) {
        return ((TableElementInfo) elementInfo).getRowExtentAt(r, c);
      }

      /**
       * Returns the number of columns occupied by the Accessible at
       * a specified row and column in the table.
       *
       * @return the number of columns occupied by the Accessible at a given specified row and
       * column
       */
      public int getAccessibleColumnExtentAt(int r, int c) {
        return ((TableElementInfo) elementInfo).getColumnExtentAt(r, c);
      }

      /**
       * Returns the row headers as an AccessibleTable.
       *
       * @return an AccessibleTable representing the row headers
       */
      public AccessibleTable getAccessibleRowHeader() {
        return rowHeadersTable;
      }

      /**
       * Sets the row headers.
       *
       * @param table an AccessibleTable representing the row headers
       */
      public void setAccessibleRowHeader(AccessibleTable table) {
      }

      /**
       * Returns the column headers as an AccessibleTable.
       *
       * @return an AccessibleTable representing the column headers
       */
      public AccessibleTable getAccessibleColumnHeader() {
        return null;
      }

      /**
       * Sets the column headers.
       *
       * @param table an AccessibleTable representing the column headers
       */
      public void setAccessibleColumnHeader(AccessibleTable table) {
      }

      /**
       * Returns the description of the specified row in the table.
       *
       * @param r zero-based row of the table
       * @return the description of the row
       */
      public Accessible getAccessibleRowDescription(int r) {
        return null;
      }

      /**
       * Sets the description text of the specified row of the table.
       *
       * @param r zero-based row of the table
       * @param a the description of the row
       */
      public void setAccessibleRowDescription(int r, Accessible a) {
      }

      /**
       * Returns the description text of the specified column in the table.
       *
       * @param c zero-based column of the table
       * @return the text description of the column
       */
      public Accessible getAccessibleColumnDescription(int c) {
        return null;
      }

      /**
       * Sets the description text of the specified column in the table.
       *
       * @param c zero-based column of the table
       * @param a the text description of the column
       */
      public void setAccessibleColumnDescription(int c, Accessible a) {
      }

      /**
       * Returns a boolean value indicating whether the accessible at
       * a specified row and column is selected.
       *
       * @param r zero-based row of the table
       * @param c zero-based column of the table
       * @return the boolean value true if the accessible at the row and column is selected.
       * Otherwise, the boolean value false
       */
      public boolean isAccessibleSelected(int r, int c) {
        if (validateIfNecessary()) {
          if (r < 0 || r >= getAccessibleRowCount() ||
              c < 0 || c >= getAccessibleColumnCount()) {
            return false;
          }
          TableCellElementInfo cell = getCell(r, c);
          if (cell != null) {
            Element elem = cell.getElement();
            int start = elem.getStartOffset();
            int end = elem.getEndOffset();
            return start >= editor.getSelectionStart() &&
                end <= editor.getSelectionEnd();
          }
        }
        return false;
      }

      /**
       * Returns a boolean value indicating whether the specified row
       * is selected.
       *
       * @param r zero-based row of the table
       * @return the boolean value true if the specified row is selected. Otherwise, false.
       */
      public boolean isAccessibleRowSelected(int r) {
        if (validateIfNecessary()) {
          if (r < 0 || r >= getAccessibleRowCount()) {
            return false;
          }
          int nColumns = getAccessibleColumnCount();

          TableCellElementInfo startCell = getCell(r, 0);
          if (startCell == null) {
            return false;
          }
          int start = startCell.getElement().getStartOffset();

          TableCellElementInfo endCell = getCell(r, nColumns - 1);
          if (endCell == null) {
            return false;
          }
          int end = endCell.getElement().getEndOffset();

          return start >= editor.getSelectionStart() &&
              end <= editor.getSelectionEnd();
        }
        return false;
      }

      /**
       * Returns a boolean value indicating whether the specified column
       * is selected.
       *
       * @param c zero-based column of the table
       * @return the boolean value true if the specified column is selected. Otherwise, false.
       */
      public boolean isAccessibleColumnSelected(int c) {
        if (validateIfNecessary()) {
          if (c < 0 || c >= getAccessibleColumnCount()) {
            return false;
          }
          int nRows = getAccessibleRowCount();

          TableCellElementInfo startCell = getCell(0, c);
          if (startCell == null) {
            return false;
          }
          int start = startCell.getElement().getStartOffset();

          TableCellElementInfo endCell = getCell(nRows - 1, c);
          if (endCell == null) {
            return false;
          }
          int end = endCell.getElement().getEndOffset();
          return start >= editor.getSelectionStart() &&
              end <= editor.getSelectionEnd();
        }
        return false;
      }

      /**
       * Returns the selected rows in a table.
       *
       * @return an array of selected rows where each element is a zero-based row of the table
       */
      public int[] getSelectedAccessibleRows() {
        if (validateIfNecessary()) {
          int nRows = getAccessibleRowCount();
          Vector<Integer> vec = new Vector<Integer>();

          for (int i = 0; i < nRows; i++) {
            if (isAccessibleRowSelected(i)) {
              vec.addElement(Integer.valueOf(i));
            }
          }
          int retval[] = new int[vec.size()];
          for (int i = 0; i < retval.length; i++) {
            retval[i] = vec.elementAt(i).intValue();
          }
          return retval;
        }
        return new int[0];
      }

      /**
       * Returns the selected columns in a table.
       *
       * @return an array of selected columns where each element is a zero-based column of the table
       */
      public int[] getSelectedAccessibleColumns() {
        if (validateIfNecessary()) {
          int nColumns = getAccessibleRowCount();
          Vector<Integer> vec = new Vector<Integer>();

          for (int i = 0; i < nColumns; i++) {
            if (isAccessibleColumnSelected(i)) {
              vec.addElement(Integer.valueOf(i));
            }
          }
          int retval[] = new int[vec.size()];
          for (int i = 0; i < retval.length; i++) {
            retval[i] = vec.elementAt(i).intValue();
          }
          return retval;
        }
        return new int[0];
      }

      // begin AccessibleExtendedTable implementation -------------

      /**
       * Returns the row number of an index in the table.
       *
       * @param index the zero-based index in the table
       * @return the zero-based row of the table if one exists; otherwise -1.
       */
      public int getAccessibleRow(int index) {
        if (validateIfNecessary()) {
          int numCells = getAccessibleColumnCount() *
              getAccessibleRowCount();
          if (index >= numCells) {
            return -1;
          } else {
            return index / getAccessibleColumnCount();
          }
        }
        return -1;
      }

      /**
       * Returns the column number of an index in the table.
       *
       * @param index the zero-based index in the table
       * @return the zero-based column of the table if one exists; otherwise -1.
       */
      public int getAccessibleColumn(int index) {
        if (validateIfNecessary()) {
          int numCells = getAccessibleColumnCount() *
              getAccessibleRowCount();
          if (index >= numCells) {
            return -1;
          } else {
            return index % getAccessibleColumnCount();
          }
        }
        return -1;
      }

      /**
       * Returns the index at a row and column in the table.
       *
       * @param r zero-based row of the table
       * @param c zero-based column of the table
       * @return the zero-based index in the table if one exists; otherwise -1.
       */
      public int getAccessibleIndex(int r, int c) {
        if (validateIfNecessary()) {
          if (r >= getAccessibleRowCount() ||
              c >= getAccessibleColumnCount()) {
            return -1;
          } else {
            return r * getAccessibleColumnCount() + c;
          }
        }
        return -1;
      }

      /**
       * Returns the row header at a row in a table.
       *
       * @param r zero-based row of the table
       * @return a String representing the row header if one exists; otherwise null.
       */
      public String getAccessibleRowHeader(int r) {
        if (validateIfNecessary()) {
          TableCellElementInfo cellInfo = getCell(r, 0);
          if (cellInfo.isHeaderCell()) {
            View v = cellInfo.getView();
            if (v != null && model != null) {
              try {
                return model.getText(v.getStartOffset(),
                    v.getEndOffset() -
                        v.getStartOffset());
              } catch (BadLocationException e) {
                return null;
              }
            }
          }
        }
        return null;
      }

      /**
       * Returns the column header at a column in a table.
       *
       * @param c zero-based column of the table
       * @return a String representing the column header if one exists; otherwise null.
       */
      public String getAccessibleColumnHeader(int c) {
        if (validateIfNecessary()) {
          TableCellElementInfo cellInfo = getCell(0, c);
          if (cellInfo.isHeaderCell()) {
            View v = cellInfo.getView();
            if (v != null && model != null) {
              try {
                return model.getText(v.getStartOffset(),
                    v.getEndOffset() -
                        v.getStartOffset());
              } catch (BadLocationException e) {
                return null;
              }
            }
          }
        }
        return null;
      }

      public void addRowHeader(TableCellElementInfo cellInfo, int rowNumber) {
        if (rowHeadersTable == null) {
          rowHeadersTable = new AccessibleHeadersTable();
        }
        rowHeadersTable.addHeader(cellInfo, rowNumber);
      }
      // end of AccessibleExtendedTable implementation ------------

      protected class AccessibleHeadersTable implements AccessibleTable {

        // Header information is modeled as a Hashtable of
        // ArrayLists where each Hashtable entry represents
        // a row containing one or more headers.
        private Hashtable<Integer, ArrayList<TableCellElementInfo>> headers =
            new Hashtable<Integer, ArrayList<TableCellElementInfo>>();
        private int rowCount = 0;
        private int columnCount = 0;

        public void addHeader(TableCellElementInfo cellInfo, int rowNumber) {
          Integer rowInteger = Integer.valueOf(rowNumber);
          ArrayList<TableCellElementInfo> list = headers.get(rowInteger);
          if (list == null) {
            list = new ArrayList<TableCellElementInfo>();
            headers.put(rowInteger, list);
          }
          list.add(cellInfo);
        }

        /**
         * Returns the caption for the table.
         *
         * @return the caption for the table
         */
        public Accessible getAccessibleCaption() {
          return null;
        }

        /**
         * Sets the caption for the table.
         *
         * @param a the caption for the table
         */
        public void setAccessibleCaption(Accessible a) {
        }

        /**
         * Returns the summary description of the table.
         *
         * @return the summary description of the table
         */
        public Accessible getAccessibleSummary() {
          return null;
        }

        /**
         * Sets the summary description of the table
         *
         * @param a the summary description of the table
         */
        public void setAccessibleSummary(Accessible a) {
        }

        /**
         * Returns the number of rows in the table.
         *
         * @return the number of rows in the table
         */
        public int getAccessibleRowCount() {
          return rowCount;
        }

        /**
         * Returns the number of columns in the table.
         *
         * @return the number of columns in the table
         */
        public int getAccessibleColumnCount() {
          return columnCount;
        }

        private TableCellElementInfo getElementInfoAt(int r, int c) {
          ArrayList<TableCellElementInfo> list = headers.get(Integer.valueOf(r));
          if (list != null) {
            return list.get(c);
          } else {
            return null;
          }
        }

        /**
         * Returns the Accessible at a specified row and column
         * in the table.
         *
         * @param r zero-based row of the table
         * @param c zero-based column of the table
         * @return the Accessible at the specified row and column
         */
        public Accessible getAccessibleAt(int r, int c) {
          ElementInfo elementInfo = getElementInfoAt(r, c);
          if (elementInfo instanceof Accessible) {
            return (Accessible) elementInfo;
          } else {
            return null;
          }
        }

        /**
         * Returns the number of rows occupied by the Accessible at
         * a specified row and column in the table.
         *
         * @return the number of rows occupied by the Accessible at a given specified (row, column)
         */
        public int getAccessibleRowExtentAt(int r, int c) {
          TableCellElementInfo elementInfo = getElementInfoAt(r, c);
          if (elementInfo != null) {
            return elementInfo.getRowCount();
          } else {
            return 0;
          }
        }

        /**
         * Returns the number of columns occupied by the Accessible at
         * a specified row and column in the table.
         *
         * @return the number of columns occupied by the Accessible at a given specified row and
         * column
         */
        public int getAccessibleColumnExtentAt(int r, int c) {
          TableCellElementInfo elementInfo = getElementInfoAt(r, c);
          if (elementInfo != null) {
            return elementInfo.getRowCount();
          } else {
            return 0;
          }
        }

        /**
         * Returns the row headers as an AccessibleTable.
         *
         * @return an AccessibleTable representing the row headers
         */
        public AccessibleTable getAccessibleRowHeader() {
          return null;
        }

        /**
         * Sets the row headers.
         *
         * @param table an AccessibleTable representing the row headers
         */
        public void setAccessibleRowHeader(AccessibleTable table) {
        }

        /**
         * Returns the column headers as an AccessibleTable.
         *
         * @return an AccessibleTable representing the column headers
         */
        public AccessibleTable getAccessibleColumnHeader() {
          return null;
        }

        /**
         * Sets the column headers.
         *
         * @param table an AccessibleTable representing the column headers
         */
        public void setAccessibleColumnHeader(AccessibleTable table) {
        }

        /**
         * Returns the description of the specified row in the table.
         *
         * @param r zero-based row of the table
         * @return the description of the row
         */
        public Accessible getAccessibleRowDescription(int r) {
          return null;
        }

        /**
         * Sets the description text of the specified row of the table.
         *
         * @param r zero-based row of the table
         * @param a the description of the row
         */
        public void setAccessibleRowDescription(int r, Accessible a) {
        }

        /**
         * Returns the description text of the specified column in the table.
         *
         * @param c zero-based column of the table
         * @return the text description of the column
         */
        public Accessible getAccessibleColumnDescription(int c) {
          return null;
        }

        /**
         * Sets the description text of the specified column in the table.
         *
         * @param c zero-based column of the table
         * @param a the text description of the column
         */
        public void setAccessibleColumnDescription(int c, Accessible a) {
        }

        /**
         * Returns a boolean value indicating whether the accessible at
         * a specified row and column is selected.
         *
         * @param r zero-based row of the table
         * @param c zero-based column of the table
         * @return the boolean value true if the accessible at the row and column is selected.
         * Otherwise, the boolean value false
         */
        public boolean isAccessibleSelected(int r, int c) {
          return false;
        }

        /**
         * Returns a boolean value indicating whether the specified row
         * is selected.
         *
         * @param r zero-based row of the table
         * @return the boolean value true if the specified row is selected. Otherwise, false.
         */
        public boolean isAccessibleRowSelected(int r) {
          return false;
        }

        /**
         * Returns a boolean value indicating whether the specified column
         * is selected.
         *
         * @param c zero-based column of the table
         * @return the boolean value true if the specified column is selected. Otherwise, false.
         */
        public boolean isAccessibleColumnSelected(int c) {
          return false;
        }

        /**
         * Returns the selected rows in a table.
         *
         * @return an array of selected rows where each element is a zero-based row of the table
         */
        public int[] getSelectedAccessibleRows() {
          return new int[0];
        }

        /**
         * Returns the selected columns in a table.
         *
         * @return an array of selected columns where each element is a zero-based column of the
         * table
         */
        public int[] getSelectedAccessibleColumns() {
          return new int[0];
        }
      }
    } // ... end AccessibleHeadersTable

    /*
     * ElementInfo for table rows
     */
    private class TableRowElementInfo extends ElementInfo {

      private TableElementInfo parent;
      private int rowNumber;

      TableRowElementInfo(Element e, TableElementInfo parent, int rowNumber) {
        super(e, parent);
        this.parent = parent;
        this.rowNumber = rowNumber;
      }

      protected void loadChildren(Element e) {
        for (int x = 0; x < e.getElementCount(); x++) {
          AttributeSet attrs = e.getElement(x).getAttributes();

          if (attrs.getAttribute(StyleConstants.NameAttribute) ==
              HTML.Tag.TH) {
            TableCellElementInfo headerElementInfo =
                new TableCellElementInfo(e.getElement(x), this, true);
            addChild(headerElementInfo);

            AccessibleTable at =
                parent.getAccessibleContext().getAccessibleTable();
            TableAccessibleContext tableElement =
                (TableAccessibleContext) at;
            tableElement.addRowHeader(headerElementInfo, rowNumber);

          } else if (attrs.getAttribute(StyleConstants.NameAttribute) ==
              HTML.Tag.TD) {
            addChild(new TableCellElementInfo(e.getElement(x), this,
                false));
          }
        }
      }

      /**
       * Returns the max of the rowspans of the cells in this row.
       */
      public int getRowCount() {
        int rowCount = 1;
        if (validateIfNecessary()) {
          for (int counter = 0; counter < getChildCount();
              counter++) {

            TableCellElementInfo cell = (TableCellElementInfo)
                getChild(counter);

            if (cell.validateIfNecessary()) {
              rowCount = Math.max(rowCount, cell.getRowCount());
            }
          }
        }
        return rowCount;
      }

      /**
       * Returns the sum of the column spans of the individual
       * cells in this row.
       */
      public int getColumnCount() {
        int colCount = 0;
        if (validateIfNecessary()) {
          for (int counter = 0; counter < getChildCount();
              counter++) {
            TableCellElementInfo cell = (TableCellElementInfo)
                getChild(counter);

            if (cell.validateIfNecessary()) {
              colCount += cell.getColumnCount();
            }
          }
        }
        return colCount;
      }

      /**
       * Overriden to invalidate the table as well as
       * TableRowElementInfo.
       */
      protected void invalidate(boolean first) {
        super.invalidate(first);
        getParent().invalidate(true);
      }

      /**
       * Places the TableCellElementInfos for this element in
       * the grid.
       */
      private void updateGrid(int row) {
        if (validateIfNecessary()) {
          boolean emptyRow = false;

          while (!emptyRow) {
            for (int counter = 0; counter < grid[row].length;
                counter++) {
              if (grid[row][counter] == null) {
                emptyRow = true;
                break;
              }
            }
            if (!emptyRow) {
              row++;
            }
          }
          for (int col = 0, counter = 0; counter < getChildCount();
              counter++) {
            TableCellElementInfo cell = (TableCellElementInfo)
                getChild(counter);

            while (grid[row][col] != null) {
              col++;
            }
            for (int rowCount = cell.getRowCount() - 1;
                rowCount >= 0; rowCount--) {
              for (int colCount = cell.getColumnCount() - 1;
                  colCount >= 0; colCount--) {
                grid[row + rowCount][col + colCount] = cell;
              }
            }
            col += cell.getColumnCount();
          }
        }
      }

      /**
       * Returns the column count of the number of columns that have
       * a rowcount >= rowspan.
       */
      private int getColumnCount(int rowspan) {
        if (validateIfNecessary()) {
          int cols = 0;
          for (int counter = 0; counter < getChildCount();
              counter++) {
            TableCellElementInfo cell = (TableCellElementInfo)
                getChild(counter);

            if (cell.getRowCount() >= rowspan) {
              cols += cell.getColumnCount();
            }
          }
          return cols;
        }
        return 0;
      }
    }

    /**
     * TableCellElementInfo is used to represents the cells of
     * the table.
     */
    private class TableCellElementInfo extends ElementInfo {

      private Accessible accessible;
      private boolean isHeaderCell;

      TableCellElementInfo(Element e, ElementInfo parent) {
        super(e, parent);
        this.isHeaderCell = false;
      }

      TableCellElementInfo(Element e, ElementInfo parent,
          boolean isHeaderCell) {
        super(e, parent);
        this.isHeaderCell = isHeaderCell;
      }

      /*
       * Returns whether this table cell is a header
       */
      public boolean isHeaderCell() {
        return this.isHeaderCell;
      }

      /*
       * Returns the Accessible representing this table cell
       */
      public Accessible getAccessible() {
        accessible = null;
        getAccessible(this);
        return accessible;
      }

      /*
       * Gets the outermost Accessible in the table cell
       */
      private void getAccessible(ElementInfo elementInfo) {
        if (elementInfo instanceof Accessible) {
          accessible = (Accessible) elementInfo;
        } else {
          for (int i = 0; i < elementInfo.getChildCount(); i++) {
            getAccessible(elementInfo.getChild(i));
          }
        }
      }

      /**
       * Returns the rowspan attribute.
       */
      public int getRowCount() {
        if (validateIfNecessary()) {
          return Math.max(1, getIntAttr(getAttributes(),
              HTML.Attribute.ROWSPAN, 1));
        }
        return 0;
      }

      /**
       * Returns the colspan attribute.
       */
      public int getColumnCount() {
        if (validateIfNecessary()) {
          return Math.max(1, getIntAttr(getAttributes(),
              HTML.Attribute.COLSPAN, 1));
        }
        return 0;
      }

      /**
       * Overriden to invalidate the TableRowElementInfo as well as
       * the TableCellElementInfo.
       */
      protected void invalidate(boolean first) {
        super.invalidate(first);
        getParent().invalidate(true);
      }
    }
  }


  /**
   * ElementInfo provides a slim down view of an Element.  Each ElementInfo
   * can have any number of child ElementInfos that are not necessarily
   * direct children of the Element. As the Document changes various
   * ElementInfos become invalidated. Before accessing a particular portion
   * of an ElementInfo you should make sure it is valid by invoking
   * <code>validateIfNecessary</code>, this will return true if
   * successful, on the other hand a false return value indicates the
   * ElementInfo is not valid and can never become valid again (usually
   * the result of the Element the ElementInfo encapsulates being removed).
   */
  private class ElementInfo {

    /**
     * The children of this ElementInfo.
     */
    private ArrayList<ElementInfo> children;
    /**
     * The Element this ElementInfo is providing information for.
     */
    private Element element;
    /**
     * The parent ElementInfo, will be null for the root.
     */
    private ElementInfo parent;
    /**
     * Indicates the validity of the ElementInfo.
     */
    private boolean isValid;
    /**
     * Indicates if the ElementInfo can become valid.
     */
    private boolean canBeValid;


    /**
     * Creates the root ElementInfo.
     */
    ElementInfo(Element element) {
      this(element, null);
    }

    /**
     * Creates an ElementInfo representing <code>element</code> with
     * the specified parent.
     */
    ElementInfo(Element element, ElementInfo parent) {
      this.element = element;
      this.parent = parent;
      isValid = false;
      canBeValid = true;
    }

    /**
     * Validates the receiver. This recreates the children as well. This
     * will be invoked within a <code>readLock</code>. If this is overriden
     * it MUST invoke supers implementation first!
     */
    protected void validate() {
      isValid = true;
      loadChildren(getElement());
    }

    /**
     * Recreates the direct children of <code>info</code>.
     */
    protected void loadChildren(Element parent) {
      if (!parent.isLeaf()) {
        for (int counter = 0, maxCounter = parent.getElementCount();
            counter < maxCounter; counter++) {
          Element e = parent.getElement(counter);
          ElementInfo childInfo = createElementInfo(e, this);

          if (childInfo != null) {
            addChild(childInfo);
          } else {
            loadChildren(e);
          }
        }
      }
    }

    /**
     * Returns the index of the child in the parent, or -1 for the
     * root or if the parent isn't valid.
     */
    public int getIndexInParent() {
      if (parent == null || !parent.isValid()) {
        return -1;
      }
      return parent.indexOf(this);
    }

    /**
     * Returns the Element this <code>ElementInfo</code> represents.
     */
    public Element getElement() {
      return element;
    }

    /**
     * Returns the parent of this Element, or null for the root.
     */
    public ElementInfo getParent() {
      return parent;
    }

    /**
     * Returns the index of the specified child, or -1 if
     * <code>child</code> isn't a valid child.
     */
    public int indexOf(ElementInfo child) {
      ArrayList children = this.children;

      if (children != null) {
        return children.indexOf(child);
      }
      return -1;
    }

    /**
     * Returns the child ElementInfo at <code>index</code>, or null
     * if <code>index</code> isn't a valid index.
     */
    public ElementInfo getChild(int index) {
      if (validateIfNecessary()) {
        ArrayList<ElementInfo> children = this.children;

        if (children != null && index >= 0 &&
            index < children.size()) {
          return children.get(index);
        }
      }
      return null;
    }

    /**
     * Returns the number of children the ElementInfo contains.
     */
    public int getChildCount() {
      validateIfNecessary();
      return (children == null) ? 0 : children.size();
    }

    /**
     * Adds a new child to this ElementInfo.
     */
    protected void addChild(ElementInfo child) {
      if (children == null) {
        children = new ArrayList<ElementInfo>();
      }
      children.add(child);
    }

    /**
     * Returns the View corresponding to this ElementInfo, or null
     * if the ElementInfo can't be validated.
     */
    protected View getView() {
      if (!validateIfNecessary()) {
        return null;
      }
      Object lock = lock();
      try {
        View rootView = getRootView();
        Element e = getElement();
        int start = e.getStartOffset();

        if (rootView != null) {
          return getView(rootView, e, start);
        }
        return null;
      } finally {
        unlock(lock);
      }
    }

    /**
     * Returns the Bounds for this ElementInfo, or null
     * if the ElementInfo can't be validated.
     */
    public Rectangle getBounds() {
      if (!validateIfNecessary()) {
        return null;
      }
      Object lock = lock();
      try {
        Rectangle bounds = getRootEditorRect();
        View rootView = getRootView();
        Element e = getElement();

        if (bounds != null && rootView != null) {
          try {
            return rootView.modelToView(e.getStartOffset(),
                Position.Bias.Forward,
                e.getEndOffset(),
                Position.Bias.Backward,
                bounds).getBounds();
          } catch (BadLocationException ble) {
          }
        }
      } finally {
        unlock(lock);
      }
      return null;
    }

    /**
     * Returns true if this ElementInfo is valid.
     */
    protected boolean isValid() {
      return isValid;
    }

    /**
     * Returns the AttributeSet associated with the Element, this will
     * return null if the ElementInfo can't be validated.
     */
    protected AttributeSet getAttributes() {
      if (validateIfNecessary()) {
        return getElement().getAttributes();
      }
      return null;
    }

    /**
     * Returns the AttributeSet associated with the View that is
     * representing this Element, this will
     * return null if the ElementInfo can't be validated.
     */
    protected AttributeSet getViewAttributes() {
      if (validateIfNecessary()) {
        View view = getView();

        if (view != null) {
          return view.getElement().getAttributes();
        }
        return getElement().getAttributes();
      }
      return null;
    }

    /**
     * Convenience method for getting an integer attribute from the passed
     * in AttributeSet.
     */
    protected int getIntAttr(AttributeSet attrs, Object key, int deflt) {
      if (attrs != null && attrs.isDefined(key)) {
        int i;
        String val = (String) attrs.getAttribute(key);
        if (val == null) {
          i = deflt;
        } else {
          try {
            i = Math.max(0, Integer.parseInt(val));
          } catch (NumberFormatException x) {
            i = deflt;
          }
        }
        return i;
      }
      return deflt;
    }

    /**
     * Validates the ElementInfo if necessary.  Some ElementInfos may
     * never be valid again.  You should check <code>isValid</code> before
     * using one.  This will reload the children and invoke
     * <code>validate</code> if the ElementInfo is invalid and can become
     * valid again. This will return true if the receiver is valid.
     */
    protected boolean validateIfNecessary() {
      if (!isValid() && canBeValid) {
        children = null;
        Object lock = lock();

        try {
          validate();
        } finally {
          unlock(lock);
        }
      }
      return isValid();
    }

    /**
     * Invalidates the ElementInfo. Subclasses should override this
     * if they need to reset state once invalid.
     */
    protected void invalidate(boolean first) {
      if (!isValid()) {
        if (canBeValid && !first) {
          canBeValid = false;
        }
        return;
      }
      isValid = false;
      canBeValid = first;
      if (children != null) {
        for (ElementInfo child : children) {
          child.invalidate(false);
        }
        children = null;
      }
    }

    private View getView(View parent, Element e, int start) {
      if (parent.getElement() == e) {
        return parent;
      }
      int index = parent.getViewIndex(start, Position.Bias.Forward);

      if (index != -1 && index < parent.getViewCount()) {
        return getView(parent.getView(index), e, start);
      }
      return null;
    }

    private int getClosestInfoIndex(int index) {
      for (int counter = 0; counter < getChildCount(); counter++) {
        ElementInfo info = getChild(counter);

        if (index < info.getElement().getEndOffset() ||
            index == info.getElement().getStartOffset()) {
          return counter;
        }
      }
      return -1;
    }

    private void update(DocumentEvent e) {
      if (!isValid()) {
        return;
      }
      ElementInfo parent = getParent();
      Element element = getElement();

      do {
        DocumentEvent.ElementChange ec = e.getChange(element);
        if (ec != null) {
          if (element == getElement()) {
            // One of our children changed.
            invalidate(true);
          } else if (parent != null) {
            parent.invalidate(parent == getRootInfo());
          }
          return;
        }
        element = element.getParentElement();
      } while (parent != null && element != null &&
          element != parent.getElement());

      if (getChildCount() > 0) {
        Element elem = getElement();
        int pos = e.getOffset();
        int index0 = getClosestInfoIndex(pos);
        if (index0 == -1 &&
            e.getType() == DocumentEvent.EventType.REMOVE &&
            pos >= elem.getEndOffset()) {
          // Event beyond our offsets. We may have represented this,
          // that is the remove may have removed one of our child
          // Elements that represented this, so, we should foward
          // to last element.
          index0 = getChildCount() - 1;
        }
        ElementInfo info = (index0 >= 0) ? getChild(index0) : null;
        if (info != null &&
            (info.getElement().getStartOffset() == pos) && (pos > 0)) {
          // If at a boundary, forward the event to the previous
          // ElementInfo too.
          index0 = Math.max(index0 - 1, 0);
        }
        int index1;
        if (e.getType() != DocumentEvent.EventType.REMOVE) {
          index1 = getClosestInfoIndex(pos + e.getLength());
          if (index1 < 0) {
            index1 = getChildCount() - 1;
          }
        } else {
          index1 = index0;
          // A remove may result in empty elements.
          while ((index1 + 1) < getChildCount() &&
              getChild(index1 + 1).getElement().getEndOffset() ==
                  getChild(index1 + 1).getElement().getStartOffset()) {
            index1++;
          }
        }
        index0 = Math.max(index0, 0);
        // The check for isValid is here as in the process of
        // forwarding update our child may invalidate us.
        for (int i = index0; i <= index1 && isValid(); i++) {
          getChild(i).update(e);
        }
      }
    }
  }

  /**
   * DocumentListener installed on the current Document.  Will invoke
   * <code>update</code> on the <code>RootInfo</code> in response to
   * any event.
   */
  private class DocumentHandler implements DocumentListener {

    public void insertUpdate(DocumentEvent e) {
      getRootInfo().update(e);
    }

    public void removeUpdate(DocumentEvent e) {
      getRootInfo().update(e);
    }

    public void changedUpdate(DocumentEvent e) {
      getRootInfo().update(e);
    }
  }

  /*
   * PropertyChangeListener installed on the editor.
   */
  private class PropertyChangeHandler implements PropertyChangeListener {

    public void propertyChange(PropertyChangeEvent evt) {
      if (evt.getPropertyName().equals("document")) {
        // handle the document change
        setDocument(editor.getDocument());
      }
    }
  }
}
